iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 16
0
自我挑戰組

Golang魔法使 ─ 30天從零開始學習Go語言 | 比Python還簡單 | 理科生一定學得會 | 文科生不好說系列 第 16

#16 Go routine ── 前無古人後無來者,地表最輕量、最快速的執行緒 | Golang魔法使

  • 分享至 

  • xImage
  •  

這天,來了新的角色,觀月歌帆,她是小櫻他們班的新老師,但這位老師的法力高強,一眼就被小狼視破,劇情究竟會怎麼發展呢?讓我們繼續看下去~

集數來到第 26 集,觀月歌帆可說是第一季的轉折點,就好比 Go routing 一樣,雖然 Golang 的錯誤處理也是經典,但 Go routing 更是經典中的經典,地表上幾乎所有的魔法都沒有 Golang 的 Go routing 強,執行迅快、輕量、簡單好上手,沒有其他魔法能同時擁有這三個特性。

執行緒是什麼?

執行緒是電腦進行排程時最小的單位。在現在的CPU中,通常可以同時處理好幾個執行緒,因為可以同時做好幾件事,可以提升整台電腦的效能。

就好比小時候被老師要求罰寫,一個人寫可能要一小時,但如果外包出去再給另一位同學寫就只需要半小時。在電腦中也是類似這樣的運作。當然並不會這麼剛好兩個人罰抄就是半小時,因為在分配工作前要先進行溝通,所以並不會說兩個人罰抄就一定能足足快兩倍。不過,如果在事前工作分配的效率就很高,那麼效率一定就越好,而能花最少時間就找到同學代抄並安排好範圍的角色非 Golang 莫屬。

利用關鍵字 go 快速建立一個執行緒

我們一般寫的程式主要都是在主執行緒上,如果要啟用另一條執行緒,只要在主執行緒中呼叫函式前加上 go 即可

package main
import "fmt"

func foo(){
    for i := 0; i<5; i = i + 1{
        fmt.Println("副執行緒")
    }
}

func main(){
    go foo()
    for i := 0; i<5; i = i + 1{
        fmt.Println("主執行緒")
    }
}

執行結果:
主執行緒
主執行緒
主執行緒
主執行緒
主執行緒

休旦幾累,副執行緒怎麼沒有被執行??

why?

因為在啟動另一條執行緒時會需要一段時間(雖然很快),但是在還沒啟動完畢時,主執行序就已經結束了,主執行緒一旦結束命令提示字元就會停止。

那麼要怎麼辦顯示「副執行緒」呢?其實只要簡單的用一個等待函式讓主執行序稍微等待一下就可了

package main
import(
    "fmt"
    "time"
)

func foo(){
    for i := 0; i < 5; i = i + 1{
        fmt.Println("副執行緒")
    }
}

func main(){
    go foo()
    for i := 0; i < 5; i = i + 1{
        fmt.Println("主執行緒")
    }
    // 讓主執行序暫停三秒,稍微等待 foo() 結束
    time.Sleep(3 * time.Second)
}

執行結果:
副執行緒
副執行緒
副執行緒
副執行緒
副執行緒
主執行緒
主執行緒
主執行緒
主執行緒
主執行緒

執行結果每次都不一樣,由作業系統決定

有沒有更好的方法?

為什麼一定要等 3 秒?其實等 1 秒也可以吧?有沒有一個更明確的方法?其實是有的,我們可以透過副執行序在完成工作後回傳一個變數,並讓主執行序等待這個變數,直到收到才結束。而這個變數因為要在執行序和執行序之間傳遞所以是個特別的型態!

在執行序間傳遞的變數 ── Channel 通道

我們可以想像一下,在執行緒之間有一個共用的通道,執行緒可以利用這個通道來交換資訊。

利用 channel 傳遞資訊通常分為三個步驟

  1. 宣告 channel 變數,利用 make 宣告
  2. 傳送資料到 channel
  3. 從 channel 得到資料

STEP1: 宣告 channel

channel 上的變數跟一般變數使用沒什麼不同,只是多了 chan 的前綴,比如原先的 int 在 channel 上叫作 chan int,宣告的方法很簡單只要使用 make() 並以型態為參數即可 make(chan int), make(chan bool),...

package main
import "fmt"

func main(){
    myChannel := make(chan bool)
    // 印出 myChannel 的型態
    fmt.Printf("%T", myChannel)
}

執行結果:
chan bool

STEP2: 傳送值到 Channel

利用 channel變數 <- 要傳的值 這個方法即可將值傳到 channel 上

package main
//import "fmt"

func foo(myChannel chan string){
    myChannel <- "封印解除!!"
}

func main(){
    myChannel := make(chan string)
    go foo(myChannel)
}

執行結果:
(什沒都沒有)

STEP3: 從 Channel 取得值

利用 要接收的變數 <- channel變數 就可以取得 channel 上的值了,要注意的一點是,「取得channel上的值」這個步驟會進行等待,一定要有值傳到 channel 上,才會接收,如果接收不到就會一直等待!

package main
import "fmt"

func foo(myChannel chan string){
    myChannel <- "封印解除!!"
}

func main(){
    myChannel := make(chan string)
    go foo(myChannel)
    receiver := <- myChannel
    fmt.Println(receiver)
}

執行結果:
封印解除!!

接收 channel 的值,不一定要真的有變數去接

就好比函式的回傳值不一定要有變數去接一樣,接收 channel 的值時不一定真的要有變數去接

package main
import "fmt"

func foo(myChannel chan string){
    fmt.Println("呼叫 foo()")
    myChannel <- "封印解除!!"
}

func main(){
    myChannel := make(chan string)
    go foo(myChannel)
    // 等待接收 channel
    // 不一定要有變數去接球
    <- myChannel
}

執行結果:
呼叫 foo()


中場休息:導演沒有要讓你們在一起就不會在一起


利用 Channel 在主執行緒上等待其他執行緒完成工作

package main
import "fmt"

func foo(foo_is_end chan bool){
    for i := 0; i < 5; i = i + 1{
        fmt.Println("副執行緒")
    }
    // 傳送 true 到 channel 上
    foo_is_end <- true
}

func main(){
    // 新增一個 channel
    foo_is_end := make(chan bool)
    // 將該 channel 傳給 foo()
    go foo(foo_is_end)
    for i := 0; i < 5; i = i + 1{
        fmt.Println("主執行緒")
    }
    // 等待 channel 有值出現
    <- foo_is_end
}

執行結果:
主執行緒
主執行緒
主執行緒
主執行緒
主執行緒
副執行緒
副執行緒
副執行緒
副執行緒
副執行緒

執行結果每次都不一樣,由作業系統決定

沒有人傳值給 channel ,卻有人在接收,這時會怎樣?

如同前面所說,在接收 channel 上的值時,會進行等待,但如果根本沒有值會被發送上來,那麼,很直覺地,程式就會一直等下去囉

package main
import "fmt"

func foo(foo_is_end chan bool){
    for i := 0; i < 5; i = i + 1{
        fmt.Println("副執行緒")
    }
    // 註解掉,不把值傳送到 channel 上
    // foo_is_end <- true
}

func main(){
    // 新增一個 channel
    foo_is_end := make(chan bool)
    // 將該 channel 傳給 foo()
    go foo(foo_is_end)
    for i := 0; i < 5; i = i + 1{
        fmt.Println("主執行緒")
    }
    // 等待 channel 有值出現
    // 但永遠等不到
    <- foo_is_end
}

執行結果:
主執行緒
主執行緒
主執行緒
主執行緒
主執行緒
副執行緒
副執行緒
副執行緒
副執行緒
副執行緒
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
C:/Users/liao2/OneDrive/go-tutorial/lesson16b.go:22 +0xea
exit status 2

人生第一個 deadlock GET!

deadlock 就是死鎖,簡單來說就是有執行緒卡住不能往前進了。而這個範例被卡住的就是主執行緒,因為它一直苦苦等著 channel 上的值出現


本文圖片大多來自:
庫洛魔法使第一季第廿六集

後面劇情真滴精彩,大家快去看


上一篇
#15 錯誤處理 Error Handling & 參數數量可變的函式 | Golang魔法使
下一篇
#17 Unbuffered Channel & Buffered Channel | Golang魔法使
系列文
Golang魔法使 ─ 30天從零開始學習Go語言 | 比Python還簡單 | 理科生一定學得會 | 文科生不好說30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言